#region File Description
//-----------------------------------------------------------------------------
// GameplayScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
#endregion

namespace VoxelEngine
{
    /// <summary>
    /// This screen implements the actual game logic.
    /// </summary>
    class GameplayScreen : GameScreen
    {
        #region Fields

        ContentManager content;
        float pauseAlpha;

        Block[, ,] testBlocks;
        VertexPositionColor[] vertices;
        IndexBuffer ib;
        BasicEffect effect;

        Vector3 cameraPosition = new Vector3(0, 0, 0);
        float leftrightRot = MathHelper.PiOver2;
        float updownRot = -MathHelper.Pi / 10.0f;
        const float rotationSpeed = 0.3f;
        const float moveSpeed = 30.0f;
        MouseState originalMouseState;
        int[] indices;
        Matrix viewMatrix;
        #endregion

        #region Initialization


        /// <summary>
        /// Constructor.
        /// </summary>
        public GameplayScreen()
        {
            TransitionOnTime = TimeSpan.FromSeconds(1.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);
        }


        /// <summary>
        /// Load graphics content for the game.
        /// </summary>
        public override void LoadContent()
        {
            if (content == null)
                content = new ContentManager(ScreenManager.Game.Services, "Content");
            Thread.Sleep(1000);

            effect = new BasicEffect(ScreenManager.Game.GraphicsDevice);

            testBlocks = new Block[1, 1, 1];
            for (int i = 0; i < testBlocks.GetLength(0); i++)
                for (int j = 0; j < testBlocks.GetLength(1); j++)
                    for (int k = 0; k < testBlocks.GetLength(2); k++)
                    {
                        testBlocks[i, j, k] = new Block();
                        testBlocks[i, j, k].Type = (i % 2 == 0) ? Block.BlockType.BlockType_Default : Block.BlockType.BlockType_Grass;
                    }

            SetupVertices();

            Mouse.SetPosition(ScreenManager.Game.GraphicsDevice.Viewport.Width / 2, ScreenManager.Game.GraphicsDevice.Viewport.Height / 2);
            originalMouseState = Mouse.GetState();
            UpdateViewMatrix();

            // once the load has finished, we use ResetElapsedTime to tell the game's
            // timing mechanism that we have just finished a very long frame, and that
            // it should not try to catch up.
            ScreenManager.Game.ResetElapsedTime();
        }

        private void SetupVertices()
        {
            vertices = new VertexPositionColor[testBlocks.GetLength(0) * testBlocks.GetLength(1) * testBlocks.GetLength(2) * 8];
            
            for (int i = 0; i < testBlocks.GetLength(0); i++)
                for (int j = 0; j < testBlocks.GetLength(1); j++)
                    for (int k = 0; k < testBlocks.GetLength(2); k++)
                    {
                        int index = (i + j * testBlocks.GetLength(0) + k * testBlocks.GetLength(1) * testBlocks.GetLength(0)) * 8;
                        vertices[index] = new VertexPositionColor(new Vector3(i, j, k), Block.ConvertBlockTypeToColor(testBlocks[i, j, k].Type));
                        vertices[index + 1] = new VertexPositionColor(new Vector3(i + 1, j, k), Block.ConvertBlockTypeToColor(testBlocks[i, j, k].Type));
                        vertices[index + 2] = new VertexPositionColor(new Vector3(i, j + 1, k), Block.ConvertBlockTypeToColor(testBlocks[i, j, k].Type)); 
                        vertices[index + 3] = new VertexPositionColor(new Vector3(i + 1, j + 1, k), Block.ConvertBlockTypeToColor(testBlocks[i, j, k].Type));
                        vertices[index + 4] = new VertexPositionColor(new Vector3(i, j, k + 1), Block.ConvertBlockTypeToColor(testBlocks[i, j, k].Type));
                        vertices[index + 5] = new VertexPositionColor(new Vector3(i + 1, j, k + 1), Block.ConvertBlockTypeToColor(testBlocks[i, j, k].Type));
                        vertices[index + 6] = new VertexPositionColor(new Vector3(i, j + 1, k + 1), Block.ConvertBlockTypeToColor(testBlocks[i, j, k].Type));
                        vertices[index + 7] = new VertexPositionColor(new Vector3(i + 1, j + 1, k + 1), Block.ConvertBlockTypeToColor(testBlocks[i, j, k].Type));
                    }

            indices = new int[testBlocks.GetLength(0) * testBlocks.GetLength(1) * testBlocks.GetLength(2) * 36];

            for (int i = 0; i < testBlocks.GetLength(0); i++)
                for (int j = 0; j < testBlocks.GetLength(1); j++)
                    for (int k = 0; k < testBlocks.GetLength(2); k++)
                    {
                        int vertexStartIndex = (i + j * testBlocks.GetLength(0) + k * testBlocks.GetLength(1) * testBlocks.GetLength(0)) * 8;
                        int indicesIndex = (i + j * testBlocks.GetLength(0) + k * testBlocks.GetLength(1) * testBlocks.GetLength(0)) * 36;

                        indices[indicesIndex] = vertexStartIndex;
                        indices[indicesIndex + 1] = vertexStartIndex + 1;
                        indices[indicesIndex + 2] = vertexStartIndex + 2;
                        indices[indicesIndex + 3] = vertexStartIndex + 2;
                        indices[indicesIndex + 4] = vertexStartIndex + 1;
                        indices[indicesIndex + 5] = vertexStartIndex + 3;

                        indices[indicesIndex + 6] = vertexStartIndex + 5;
                        indices[indicesIndex + 7] = vertexStartIndex + 4;
                        indices[indicesIndex + 8] = vertexStartIndex + 6;
                        indices[indicesIndex + 9] = vertexStartIndex + 5;
                        indices[indicesIndex + 10] = vertexStartIndex + 6;
                        indices[indicesIndex + 11] = vertexStartIndex + 7;

                        indices[indicesIndex + 12] = vertexStartIndex + 1;
                        indices[indicesIndex + 13] = vertexStartIndex + 0;
                        indices[indicesIndex + 14] = vertexStartIndex + 4;
                        indices[indicesIndex + 15] = vertexStartIndex + 5;
                        indices[indicesIndex + 16] = vertexStartIndex + 1;
                        indices[indicesIndex + 17] = vertexStartIndex + 4;

                        //TODO
                        indices[indicesIndex + 18] = vertexStartIndex + 0;
                        indices[indicesIndex + 19] = vertexStartIndex + 1;
                        indices[indicesIndex + 20] = vertexStartIndex + 2;
                        indices[indicesIndex + 21] = vertexStartIndex + 0;
                        indices[indicesIndex + 22] = vertexStartIndex + 1;
                        indices[indicesIndex + 23] = vertexStartIndex + 2;

                        indices[indicesIndex + 24] = vertexStartIndex + 0;
                        indices[indicesIndex + 25] = vertexStartIndex + 1;
                        indices[indicesIndex + 26] = vertexStartIndex + 2;
                        indices[indicesIndex + 27] = vertexStartIndex + 0;
                        indices[indicesIndex + 28] = vertexStartIndex + 1;
                        indices[indicesIndex + 29] = vertexStartIndex + 2;

                        indices[indicesIndex + 30] = vertexStartIndex + 0;
                        indices[indicesIndex + 31] = vertexStartIndex + 1;
                        indices[indicesIndex + 32] = vertexStartIndex + 2;
                        indices[indicesIndex + 33] = vertexStartIndex + 0;
                        indices[indicesIndex + 34] = vertexStartIndex + 1;
                        indices[indicesIndex + 35] = vertexStartIndex + 2;
                    }

            ib = new IndexBuffer(ScreenManager.GraphicsDevice, typeof(int), indices.Length, BufferUsage.None);
            ib.SetData<int>(indices);
        }

        /// <summary>
        /// Unload graphics content used by the game.
        /// </summary>
        public override void UnloadContent()
        {
            content.Unload();
        }


        #endregion

        #region Update and Draw


        /// <summary>
        /// Updates the state of the game. This method checks the GameScreen.IsActive
        /// property, so the game will stop updating when the pause menu is active,
        /// or if you tab away to a different application.
        /// </summary>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus,
                                                       bool coveredByOtherScreen)
        {
            base.Update(gameTime, otherScreenHasFocus, false);

            // Gradually fade in or out depending on whether we are covered by the pause screen.
            if (coveredByOtherScreen)
                pauseAlpha = Math.Min(pauseAlpha + 1f / 32, 1);
            else
                pauseAlpha = Math.Max(pauseAlpha - 1f / 32, 0);

            if (IsActive)
            {
                float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;
                ProcessInput(timeDifference); UpdateViewMatrix();
            }
        }

        private void ProcessInput(float amount)
        {
            MouseState currentMouseState = Mouse.GetState();
            if (currentMouseState != originalMouseState)
            {
                float xDifference = currentMouseState.X - originalMouseState.X;
                float yDifference = currentMouseState.Y - originalMouseState.Y;
                leftrightRot -= rotationSpeed * xDifference * amount;
                updownRot -= rotationSpeed * yDifference * amount;
                Mouse.SetPosition(ScreenManager.Game.GraphicsDevice.Viewport.Width / 2, ScreenManager.Game.GraphicsDevice.Viewport.Height / 2);
                UpdateViewMatrix();
            }

            Vector3 moveVector = new Vector3(0, 0, 0);
            KeyboardState keyState = Keyboard.GetState();
            if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W))
                moveVector += new Vector3(0, 0, -1);
            if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S))
                moveVector += new Vector3(0, 0, 1);
            if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D))
                moveVector += new Vector3(1, 0, 0);
            if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A))
                moveVector += new Vector3(-1, 0, 0);
            if (keyState.IsKeyDown(Keys.Q))
                moveVector += new Vector3(0, 1, 0);
            if (keyState.IsKeyDown(Keys.Z))
                moveVector += new Vector3(0, -1, 0);
            AddToCameraPosition(moveVector * amount);
        }

        private void AddToCameraPosition(Vector3 vectorToAdd)
        {
            Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot);
            Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation);
            cameraPosition += moveSpeed * rotatedVector;
            UpdateViewMatrix();
        }

        private void UpdateViewMatrix()
        {
            Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot);

            Vector3 cameraOriginalTarget = new Vector3(0, 0, -1);
            Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0);

            Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation);
            Vector3 cameraFinalTarget = cameraPosition + cameraRotatedTarget;

            Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation);

            viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraRotatedUpVector);
        }
        /// <summary>
        /// Lets the game respond to player input. Unlike the Update method,
        /// this will only be called when the gameplay screen is active.
        /// </summary>
        public override void HandleInput(InputState input)
        {
            if (input == null)
                throw new ArgumentNullException("input");

            // Look up inputs for the active player profile.
            int playerIndex = (int)ControllingPlayer.Value;

            KeyboardState keyboardState = input.CurrentKeyboardStates[playerIndex];
            GamePadState gamePadState = input.CurrentGamePadStates[playerIndex];

            // The game pauses either if the user presses the pause button, or if
            // they unplug the active gamepad. This requires us to keep track of
            // whether a gamepad was ever plugged in, because we don't want to pause
            // on PC if they are playing with a keyboard and have no gamepad at all!
            bool gamePadDisconnected = !gamePadState.IsConnected &&
                                       input.GamePadWasConnected[playerIndex];

            if (input.IsPauseGame(ControllingPlayer) || gamePadDisconnected)
            {
                ScreenManager.AddScreen(new PauseMenuScreen(), ControllingPlayer);
            }
            else
            {
            }
        }


        /// <summary>
        /// Draws the gameplay screen.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            // This game has a blue background. Why? Because!
            ScreenManager.GraphicsDevice.Clear(ClearOptions.Target,
                                               Color.CornflowerBlue, 0, 0);
            ScreenManager.Game.Window.Title = "" + cameraPosition;
            //Set effect World,view,proj
            effect.World = Matrix.Identity;
            effect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(60.0f), ScreenManager.Game.GraphicsDevice.Viewport.AspectRatio, 1.0f, 1000.0f);
            effect.View = viewMatrix;
            effect.VertexColorEnabled = true;
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();

                ScreenManager.Game.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices,0, ib.IndexCount / 3);
            }
            
            // If the game is transitioning on or off, fade it out to black.
            if (TransitionPosition > 0 || pauseAlpha > 0)
            {
                float alpha = MathHelper.Lerp(1f - TransitionAlpha, 1f, pauseAlpha / 2);

                ScreenManager.FadeBackBufferToBlack(alpha);
            }
        }


        #endregion
    }
}
